/**********************************************************************\
 © COPYRIGHT 2006 Corporation for National Research Initiatives (CNRI);
                        All rights reserved.
               
        The HANDLE.NET software is made available subject to the
         Handle System Public License, which may be obtained at
         http://hdl.handle.net/4263537/5030 or hdl:4263537/5030
\**********************************************************************/

package net.handle.apps.servlet_proxy;

import java.io.*;
import java.text.*;
import java.util.*;
import javax.servlet.*;
import javax.servlet.http.*;
import java.net.URLEncoder;
import java.lang.reflect.*;

import net.handle.hdllib.*;
import net.handle.util.StringUtils;

public class HDLProxy
  extends HttpServlet
  implements SingleThreadModel
{
  String HTDOCS = "";

  public static HandleResolver resolver = null;
  private Properties config;
  private RotatingAccessLog logger;
  private boolean logReferrer = false;
  private boolean logHSAdmin = false;

  // sfx
  boolean sfxEnabled;
  String sfxCookieName;    

  // current state variables;
  HttpServletRequest req;
  HttpParams params = new HttpParams();
  HttpServletResponse resp;
  private MessageEnvelope envelope = new MessageEnvelope();
  long intime = -1L;  // time the request first arrived

  private byte envelopeBuf[] = new byte[Common.MESSAGE_ENVELOPE_SIZE];
  private byte messageBuf[] = new byte[DEFAULT_MAX_MESSAGE_LENGTH];
    
  // constants
  private static final int DEFAULT_MAX_MESSAGE_LENGTH = 1024;
  public static final byte MSG_INVALID_MSG_SIZE[] =
        Util.encodeString("Invalid message length");
  public static final byte MSG_INVALID_REQUEST[] =
        Util.encodeString("Invalid request");


  // Map of type handlers, set from config props
  HashMap typeHandlers;
  class TypeHandlerEntry {
    TypeHandler handler;
    int position;
  }
  TypeHandler valueHandlers[] = {};
    
  // virtual host -> page mappings
  Hashtable queryPages = new Hashtable();
  Hashtable helpPages = new Hashtable();
  Hashtable errorPages = new Hashtable();
  Hashtable responsePages = new Hashtable();
  Hashtable valuesNotFoundPages = new Hashtable();

  public synchronized void destroy() {
    loadedSettings = false;
    logger = null;
  }

  private boolean loadedSettings = false;
  synchronized void loadSettings()
    throws ServletException
  {
    if(loadedSettings) return;

    System.err.println("initializing DOI/HDL proxy servlet");
    
    // init type handler map, with sort on position 
    typeHandlers = new HashMap();
    
    // load configuration properties
    config = new Properties();
    try {
      config.load(getClass().getResourceAsStream("hdlproxy.properties"));
    } catch (IOException e){
      throw new ServletException("Error loading servlet properties: "+e);
    }

    String conffile = getServletConfig().getInitParameter("config");
    if (conffile != null){
      try {
        config.load(new FileInputStream(conffile));
      } catch (Exception e) {
        throw new ServletException("Error loading "+conffile);
      }
    }
    // htdocs dir
    HTDOCS = config.getProperty("htdocs");

    // get the singleton logger so that we don't clobber the logs from other servlets
    System.err.println("loading logger for hdl/doi proxy");    
    try {
      String logFileName = config.getProperty("access_log");
      if(logFileName!=null) {
        File logDir = new File(logFileName).getParentFile();
        logger = RotatingAccessLog.getMonthlyLogger(logDir);
      }
    } catch (Exception e){
      throw new ServletException("Error loading logger: "+e);
    }

    sfxEnabled = config.get("sfx_cookie")!=null;
    sfxCookieName = (String)config.get("sfx_cookie");
    logReferrer = Boolean.valueOf(config.getProperty("log_referrer","false")).booleanValue();
    logHSAdmin = Boolean.valueOf(config.getProperty("log_hs_admin","false")).booleanValue();

    // create a sorted list of handlers 
    TreeSet handlers = new TreeSet(new Comparator() {
      public int compare(Object o1, Object o2) {
        return ((TypeHandlerEntry)o1).position - ((TypeHandlerEntry)o2).position;
      }
      public boolean equals(Object o) {
        return o==this;
      }
    });
    
    // iterate through rest of config options for more control
    for (Enumeration enumer=config.propertyNames(); enumer.hasMoreElements();){
      String prop = (String)enumer.nextElement();
      // type handler setting
      if (prop.startsWith("typehandler.")){
        TypeHandlerEntry entry = new TypeHandlerEntry();
        String klass = config.getProperty(prop);
        try {
          String pos = prop.substring("typehandler.".length());
          entry.position = Integer.parseInt(pos);
          entry.handler = (TypeHandler)getClass().forName(klass).newInstance();
          handlers.add(entry);
        } catch (Exception e) {
          System.err.print("Error setting "+prop+" = "+klass+": ");
          System.out.println(e.toString()+"\n"); 
          e.printStackTrace();
        }
      }
      // html pages
      else if (prop.startsWith("query-page.")){
        try {
          String host = prop.substring("query-page.".length()).toLowerCase();
          String file = config.getProperty(prop).trim();
          queryPages.put(host, new HTMLFile(HTDOCS, file));
        }
        catch (Exception e){
          System.err.println("Error setting query page: "+prop);
          System.out.println(e.toString()+"\n"); 
        }
      }
      else if (prop.startsWith("help-page.")){
        try {
          String host = prop.substring("help-page.".length()).toLowerCase();
          String file = config.getProperty(prop).trim();
          helpPages.put(host, new HTMLFile(HTDOCS, file));
        }
        catch (Exception e){
          System.err.println("Error setting help page: "+prop);
          System.out.println(e.toString()+"\n"); 
        }
      }
      else if (prop.startsWith("response-page.")){
        try {
          String host = prop.substring("response-page.".length()).toLowerCase();
          String file = config.getProperty(prop).trim();
          responsePages.put(host, new HTMLFile(HTDOCS, file));
        }
        catch (Exception e) {
          System.err.println("Error setting response page: "+prop);
        }
      }
      else if (prop.startsWith("novalues-page.")){
        try {
          String host = prop.substring("novalues-page.".length()).toLowerCase();
          String file = config.getProperty(prop).trim();
          valuesNotFoundPages.put(host, new HTMLFile(HTDOCS, file));
        }
        catch (Exception e) {
          System.err.println("Error setting valuesnotfound page: "+prop);
        }
      }
      else if (prop.startsWith("error-page.")){
        try {
          String host = prop.substring("error-page.".length()).toLowerCase();
          String file = config.getProperty(prop).trim();
          errorPages.put(host, new HTMLFile(HTDOCS, file));
        }
        catch (Exception e){
          System.err.println("Error setting error page: "+prop);
        }
      }
    }

    // copy the sorted list of handlers into a faster array
    valueHandlers = new TypeHandler[handlers.size()];
    Iterator iter = handlers.iterator();
    for(int i=0; i<valueHandlers.length; i++) {
      valueHandlers[i] = ((TypeHandlerEntry)iter.next()).handler;
    }
    
    // init resolver and set cache
    if (resolver == null){
      resolver = new HandleResolver();
      //resolver.traceMessages = true;
      //MemCache cache = new MemCache();
      //cache.setMaximumHandles(1024*16);
      try {
        String dir = System.getProperty("java.io.tmpdir")+File.separator;
        System.err.println("Using cache "+dir+"cache.jdb"); 
        Cache cache = new JDBCache(new File(dir+"cache.jdb"));
        resolver.setCache(cache);
      } catch (Exception e){
        System.err.println("Error setting cache: "+e); 
      }
    }
    loadedSettings = true;
  }

  public synchronized void doGet(HttpServletRequest req, HttpServletResponse resp)
    throws IOException, ServletException
  {
    loadSettings();
    
    intime = System.currentTimeMillis();

    String hdl = req.getRequestURI();
    if (hdl.startsWith("/")) hdl = hdl.substring(1).trim();

    // parse GET params
    params.clear();
    String query = req.getQueryString();
    if (query != null){
      StringTokenizer st = new StringTokenizer(query, "&;");
      while (st.hasMoreTokens()){
          String s = st.nextToken();
          String key, val;
          int pos = s.indexOf('=');
          if (pos >= 0){
            key = s.substring(0, pos);
            val = s.substring(pos+1);
          }
          else {
            key = s;
            val = "";
          }
          params.addParameter(key, val);
          // if ("id".equals(key)) hdl = val;
      }
    }

    doResponse(hdl, req, resp);
    close();
  }

  public synchronized void doPost(HttpServletRequest req, HttpServletResponse resp)
      throws IOException, ServletException
  {
    loadSettings();
    
    intime = System.currentTimeMillis();
    params.clear();
    for (Enumeration e=req.getParameterNames(); e.hasMoreElements();){
      String key = (String)e.nextElement();
      params.addParameters(key, (String[])req.getParameterValues(key));
    }
    String hdl = req.getParameter("hdl");
    if (hdl!=null) hdl = hdl.trim();

    doResponse(hdl, req, resp);
    close();
  }

  public synchronized void close() {
    try { 
      req.getInputStream().close();
      resp.getOutputStream().close();
    }
    catch (Exception e){ 
    }
  }

  void doResponse(String hdl, HttpServletRequest req, HttpServletResponse resp)
    throws IOException, ServletException
  {
    if (hdl!=null) {
      // remove any namespace prefixes
      String lowerHdl = hdl.toLowerCase();
      if(lowerHdl.startsWith("info:doi/") || lowerHdl.startsWith("info:hdl/")) {
        hdl = hdl.substring(9);
      } else if(lowerHdl.startsWith("hdl:") || lowerHdl.startsWith("doi:")) {
        hdl = hdl.substring(4);
      }
    }
    
    this.req = req;
    this.resp = resp;

    
    String accept = req.getHeader("Accept");
    if(accept!=null &&
       accept.toUpperCase().indexOf(Common.HDL_MIME_TYPE.toUpperCase())>=0){
      doHdl(hdl);
    } else {
      doResolution(hdl);
    }
  }

  void doResolution(String hdl)
    throws IOException, ServletException
  {
    doResolution(hdl, 5);
  }
  
  void doHdl(String hdl) 
    throws IOException, ServletException 
  {
    int r, n=0;
    
    InputStream in = req.getInputStream();

    // ignore extra newline char, if necessary
    r = in.read();
    if(r!='\n' && r!='\r') {
      envelopeBuf[0] = (byte)r;
      n++;
    }

    // receive and parse the message envelope
    while(n < Common.MESSAGE_ENVELOPE_SIZE &&
          (r=in.read(envelopeBuf, n, Common.MESSAGE_ENVELOPE_SIZE-n))>=0)
      n += r;

    if(n < Common.MESSAGE_ENVELOPE_SIZE) {
      // not all of the envelope was received...
      doHdlResponse(new ErrorResponse(AbstractMessage.OC_RESERVED,
                                      AbstractMessage.RC_ERROR,
                                       MSG_INVALID_MSG_SIZE), hdl);
      return;
    }

    try {
      Encoder.decodeEnvelope(envelopeBuf, envelope);
    }
    catch (HandleException e){
      doHdlResponse(new ErrorResponse(AbstractMessage.OC_RESERVED,
                                       AbstractMessage.RC_ERROR,
                    Util.encodeString("Error decoding request")), hdl);
    }

    if(envelope.messageLength > Common.MAX_MESSAGE_LENGTH ||
       envelope.messageLength < 0) {
      doHdlResponse(new ErrorResponse(AbstractMessage.OC_RESERVED,
                                       AbstractMessage.RC_ERROR,
                                       MSG_INVALID_MSG_SIZE), hdl);
      return;
    }

    // increase the messageBuf size if necessary
    if(messageBuf.length < envelope.messageLength)
      messageBuf = new byte[envelope.messageLength];

    // receive the rest of the message
    r = n = 0;
    while(n<envelope.messageLength &&
          (r=in.read(messageBuf, n, envelope.messageLength-n))>=0)
      n += r;

    AbstractRequest hdlreq = null;
    try {
      if(n<envelope.messageLength)
        throw new HandleException(HandleException.MESSAGE_FORMAT_ERROR,
                                "Expecting "+envelope.messageLength+" bytes, "+
                                "only received "+n);


      hdlreq = (AbstractRequest)Encoder.decodeMessage(messageBuf, 0, envelope);
      AbstractResponse hdlresp = resolver.processRequest(hdlreq);
      doHdlResponse(hdlresp, hdl);
    }
    catch (HandleException e){
      // e.printStackTrace();
      doHdlResponse(new ErrorResponse(AbstractMessage.OC_RESERVED, 
                        AbstractMessage.RC_ERROR,
                    Util.encodeString(e.getMessage())), hdl);
    }
  }

  void doHdlResponse(AbstractResponse hdlresp, String hdl){
    OutputStream out = null;
    try {
      byte msg[] = hdlresp.getEncodedMessage();
      boolean encrypted = false;

      envelope.sessionId = hdlresp.sessionId;
      envelope.requestId = hdlresp.requestId;
      envelope.messageId = 0;
      envelope.messageLength = msg.length;
      
      Encoder.encodeEnvelope(envelope, envelopeBuf);

      out = new BufferedOutputStream(resp.getOutputStream(), 512);

      resp.setContentType(Common.HDL_MIME_TYPE);
      resp.setContentLength(envelopeBuf.length+msg.length);
      out.write(envelopeBuf);
      out.write(msg);

      logAccess("HTTP:HDL", hdlresp.opCode, hdlresp.responseCode, req, hdl, null,
                req.getHeader("Referer"), System.currentTimeMillis()-intime);
      

      // if the response is "streamable," send the streamed part...
      if(hdlresp.streaming) {
        hdlresp.streamResponse(out);
      }
    } catch (Exception e) {
      System.err.println("Exception sending response: "+e);
      // e.printStackTrace(System.err);
    } finally {
      if(out!=null) {
        try { out.flush(); } catch (Exception e) {}
        try { out.close(); } catch (Exception e) {}
      }
    }
  }

  void doResolution(String hdl, int aliasCount) 
    throws IOException, ServletException
  {
    if (hdl==null || hdl.length()<=0) {
      // no req
      returnQueryPage();
      return;
    }

    if (hdl.equals("help.html")) {
      returnHelpPage();
      return;
    }
    
    if (req.getMethod().equals("GET")) {
      hdl = StringUtils.decodeURLIgnorePlus(hdl);
    }
    
    // do sfx redirect if sfx cookie is present
    if (sfxEnabled && req.getCookies()!=null) {
      for (int i=0; i<req.getCookies().length; i++){
        Cookie cookie = req.getCookies()[i];
        if (cookie!=null && cookie.getName().equals(sfxCookieName)){
          String sfxBaseURL = cookie.getValue();
          if (sfxBaseURL!=null && params.getParameter("nosfx")==null && params.getParameter("nols")==null) {
            if (sfxBaseURL.startsWith("\"")) sfxBaseURL=sfxBaseURL.substring(1);
            if (sfxBaseURL.endsWith("\"")){
              sfxBaseURL = sfxBaseURL.substring(0, sfxBaseURL.length()-1);
            }
            // String url = URLEncoder.encode(req.getRequestURI());
            String url;
            String queryStr = req.getQueryString();
            if (queryStr!=null && queryStr.length()>0) {
              // If there was a query string, it was due to an unencoded question
              // mark in the handle, and encode it for the open URL version.
              url = URLEncoder.encode(hdl+"?"+req.getQueryString());
            } else {
              url = URLEncoder.encode(hdl);
            }
            
            logAccess("HTTP:SFX", AbstractResponse.OC_RESOLUTION, 0, req, hdl, null,
                      req.getHeader("Referer"), System.currentTimeMillis()-intime);

            resp.sendRedirect(sfxBaseURL+"?id=doi:"+url);
            return;
          }
        }
      }
    }

    // resolve handle
    ResolutionRequest hdlreq;
    byte types[][] = null;
    int indices[] = null;
    try {
      types = getRequestedTypes();
      indices = getRequestedIndices();
    }
    catch (Exception e){
      String ref = req.getHeader("Referer");
      returnErrorPage("Error parsing parameters: "+e.toString(), hdl, ref, null);
      e.printStackTrace();
      return;
    }
    hdlreq = new ResolutionRequest(Util.encodeString(hdl), types, indices, null);
    hdlreq.authoritative = params.getParameter("auth") != null;
    HandleValue vals[] = null;
    AbstractResponse hdlresp = null;

    try {
      hdlresp = resolver.processRequest(hdlreq);

      if (hdlresp==null || hdlresp.responseCode!=AbstractMessage.RC_SUCCESS) {
        // print out friendly message
        String msg = "";
        if (resp == null) {
          msg = "Resolution Error";
        } else if (hdlresp.responseCode == AbstractMessage.RC_HANDLE_NOT_FOUND) {
          msg = "Not Found";
        } else if (hdlresp.responseCode == AbstractMessage.RC_VALUES_NOT_FOUND) {
          // should this not be an error?  maybe just show an empty response
          // page?
          msg ="No Values Found";
          if(types!=null && types.length>0) {
            // if the user was querying for specific types, return a special
            // do-you-want-to-get-more-info error page
            logAccess("HTTP:HDL", AbstractResponse.OC_RESOLUTION, 
                      hdlresp.responseCode, req, hdl, null, req.getHeader("Referer"),
                      System.currentTimeMillis()-intime);
            returnValuesNotFoundPage(msg, hdl, req.getHeader("Referer"), null);
            return;
          }
          
        } else {
          msg = "Resolution Error";
        }
        logAccess("HTTP:HDL", AbstractResponse.OC_RESOLUTION, 
                  hdlresp.responseCode, req, hdl, null, req.getHeader("Referer"),
                  System.currentTimeMillis()-intime);
        String ref = req.getHeader("Referer");
        returnErrorPage(msg, hdl, ref, null);
        return;
      }
      vals = ((ResolutionResponse)hdlresp).getHandleValues();
    }
    catch (HandleException e){
      String msg = "";
      if (e.getCode() == HandleException.SERVICE_NOT_FOUND){
        int index = hdl.indexOf("/");
        String na = (index != -1) ? hdl.substring(0, index) : hdl;
        msg = "Naming Authority [" + na + "] Not Found";
      }
      else if (e.getCode() == HandleException.CANNOT_CONNECT_TO_SERVER){
        msg = "Cannot Connect to Server";
      }
      else {
        msg = "System Error";
      }
      returnErrorPage(msg, hdl, req.getHeader("Referer"), null);
      return;
    }

    // if not disabled, follow aliases
    if (params.getParameter("ignore_aliases")==null && aliasCount>0){
      for(int i=0; i<vals.length; i++) {
         if (vals[i].hasType(Common.STD_TYPE_HSALIAS)){
            doResolution(Util.decodeString(vals[i].getData()), aliasCount--);
            return;
         }
      }
    }
    
    String noRedirect = params.getParameter("noredirect");
    if (noRedirect == null) doRedirect(hdl, vals);
    else returnResponsePage(hdl, vals);

    logAccess("HTTP:HDL", AbstractResponse.OC_RESOLUTION, 
              hdlresp.responseCode, req, hdl, vals, req.getHeader("Referer"),
              System.currentTimeMillis()-intime);
  }


  int[] getRequestedIndices() {
    // parse requested types and indices from http request
    int indices[] = null;
    String s[] = params.getParameterValues("index");
    if (s != null){
      indices = new int[s.length];
      for (int i=0; i<s.length; i++) indices[i] = Integer.parseInt(s[i]);
    }
    return indices;
  }

  byte[][] getRequestedTypes(){
    // parse requested types and indices from http request
    byte types[][] = null;
    String s[] = params.getParameterValues("type");
    if (s != null){
      types = new byte[s.length][];
      for (int i=0; i<s.length; i++) types[i] = Util.encodeString(s[i]);
    }
    return types;
  }

  /** Returns a browser redirect based on the queried values.  This is only 
    * called if the 'noredirect' flag is not set.  This method should scan
    * the list of response handlers for the most appropriate one to handle
    * the response.
    */
  private void doRedirect(String hdl, HandleValue vals[])
      throws IOException, ServletException
  {
    // iterate over the handlers until one can handle the response
    for(int i=0; i<valueHandlers.length; i++) {
      TypeHandler handler = valueHandlers[i];
      if(handler.canRedirect(vals)) {
        try {
          if(handler.doRedirect(req, resp, params, hdl, vals))
            return;
        } catch (Exception e) {
          logger.logError(RotatingAccessLog.ERRLOG_LEVEL_NORMAL,
                          "Error showing redirect for '"+hdl+"': "+e);
          returnErrorPage("Error in "+vals[i].getTypeAsString()+": "+
                          e.getMessage(), hdl, req.getHeader("Referer"), null);
          return;
        }
      }
    }
    
    // no handler returned a response, so return results page
    returnResponsePage(hdl, vals);
  }
  
  private void returnErrorPage(String msg, String hdl, String trace) 
    throws IOException
  {
    returnErrorPage(msg, hdl, null, trace);
  }

  
  private void returnValuesNotFoundPage(String msg, String hdl, String ref, String trace) 
    throws IOException
  {
    if (msg == null) msg = "Requested Values Not Found";
    if (hdl == null) hdl = "";
    if (ref == null) ref = "";
    if (trace == null) trace = "";
    
    HTMLFile f =  null;
    try {
      f = (HTMLFile)valuesNotFoundPages.get(req.getServerName().toLowerCase());
    } catch (NullPointerException e){
    }
    if (f == null) {
      f = (HTMLFile)valuesNotFoundPages.get("default");
    }
    if (f == null) { // no values-not-found page was found... resort to the error page
      returnErrorPage(msg, hdl, ref, trace);
      return;
    }
    
    f.reset();
    f.setValue("HANDLE_URL", getURLForHandle(hdl));
    f.setValue("HANDLE", hdl);
    f.setValue("ERROR", msg);
    f.setValue("REFERER", ref);
    f.setValue("TRACE", trace);
    
    resp.setContentType("text/html; charset=utf-8");
    f.output(resp.getOutputStream());
  }

  public final String getURLForHandle(String handle) {
    String baseURL = getBaseURL();
    try {
      if(baseURL.endsWith("/"))
        return baseURL+URLEncoder.encode(handle, "UTF-8");
      else
        return baseURL+"/"+URLEncoder.encode(handle, "UTF-8");
    } catch (UnsupportedEncodingException e) {
      System.err.println("Encoding exception in getURLForHandle(): "+e+" handle="+handle);
      return baseURL+"/"+URLEncoder.encode(handle);
    }
  }
  
  /** Get the base URL for this resolver */
  private String getBaseURL() {
    String servletPath = req.getServletPath();
    if(servletPath==null || servletPath.trim().length()<=0)
      servletPath = "/";
    int servletPort = req.getServerPort();
    if(servletPort<0 || servletPort==80) {
      return "http://"+req.getServerName()+req.getServletPath();
    } else {
      return "http://"+req.getServerName()+":"+servletPort+req.getServletPath();
    }
      
  }
  
  private void returnErrorPage(String msg, String hdl, String ref, String trace) 
    throws IOException 
  {
    if (msg == null) msg = "Unknown error.";
    if (hdl == null) hdl = "";
    if (ref == null) ref = "";
    if (trace == null) trace = "";
    
    HTMLFile f =  null;
    try {
      f = (HTMLFile)errorPages.get(req.getServerName().toLowerCase());
    } catch (NullPointerException e){
    }
    if (f == null) f = (HTMLFile)errorPages.get("default");
    if (f == null) {
      System.err.println("Error loading error page.");
      resp.setContentType("text/plain");
      resp.getWriter().println("Page not found!");
      return;
    }

    f.reset();
    f.setValue("HANDLE", hdl);
    f.setValue("ERROR", msg);
    f.setValue("REFERER", ref);
    f.setValue("TRACE", trace);

    resp.setContentType("text/html; charset=utf-8");
    f.output(resp.getOutputStream());
  }

  private void returnResponsePage(String hdl, HandleValue vals[])
    throws IOException 
  {
    if(vals!=null && vals.length<=0) {
      returnValuesNotFoundPage(null, hdl, req.getHeader("Referer"), null);
      return;
    }
    
    HTMLFile f = (HTMLFile)responsePages.get(req.getServerName().toLowerCase());
    if (f == null) f = (HTMLFile)responsePages.get("default");

    StringBuffer page = new StringBuffer();
    if (vals!=null && vals.length > 0) {
      page.append("<tr><td align=\"left\" valign=\"top\">Index</td>");
      page.append("<td align=\"left\" valign=\"top\">Type</td>");
      page.append("<td align=\"left\" valign=\"top\">Timestamp");
      page.append("</td><td align=\"left\" valign=\"top\">Data</td>");
      page.append("</tr>\n");
      for(int i=0; i<vals.length; i++) {
        HandleValue val = vals[i];
        TypeHandler t = null;
        for(int j=0; j<valueHandlers.length; j++) {
          if(valueHandlers[j].canFormat(val)) {
            t = valueHandlers[j];
            break;
          }
        }
        String dataStr = (t==null) ? 
          htmlEscape(val.getDataAsString()) : t.toHTML(hdl, val);
        
        page.append("<tr bgcolor=\"#"+ (i%2==0? "dddddd":"ffffff")+"\">");
        page.append("<td align=\"left\" valign=\"top\"><b>");
        page.append(val.getIndex()+"</b>");
        page.append("</td><td align=\"left\" valign=\"top\"><b>");
        page.append(htmlEscape(val.getTypeAsString()));
        page.append("</b></td><td valign=\"top\">");
        page.append(htmlEscape(val.getNicerTimestampAsString()));
        page.append("</td>\n");
        page.append("<td>"+dataStr+"</td>");
        page.append("</tr>\n");
      }
    } else {

      page.append("<tr><td align=CENTER><b>No values found.</b></td></tr>");
      
    }

    f.reset();
    f.setValue("HANDLE", hdl);
    f.setValue("VALUES", page.toString());

    resp.setContentType("text/html; charset=utf-8");
    f.output(resp.getOutputStream());
  }

  private void returnQueryPage()
    throws IOException
  {
    HTMLFile f = (HTMLFile)queryPages.get(req.getServerName().toLowerCase());
    if (f == null) f = (HTMLFile)queryPages.get("default");
    if (f == null){
      resp.setContentType("text/plain");
      resp.getWriter().println("Page not found!");
      return;
    }
    resp.setContentType("text/html; charset=utf-8");
    f.output(resp.getOutputStream());
  }

  private void returnHelpPage()
    throws IOException
  {
    HTMLFile f = (HTMLFile)helpPages.get(req.getServerName().toLowerCase());
    if (f == null) f = (HTMLFile)helpPages.get("default");
    if (f == null){
      resp.setContentType("text/plain");
      resp.getWriter().println("Page not found!");
      return;
    }
    resp.setContentType("text/html; charset=utf-8");
    f.output(resp.getOutputStream());
  }

  public static final String htmlEscape(String str) {
    if(str==null) return "null";
    StringBuffer sb = new StringBuffer();
    int sz = str.length();
    int idx = 0;
    for(int i=0; i<sz; i++) {
      char ch = str.charAt(i);
      if(ch=='&') sb.append("&amp;");
      else if(ch=='<') sb.append("&lt;");
      else if(ch=='>') sb.append("&gt;");
      else if(ch=='"') sb.append("&quot;");
      else if(ch=='\n' || ch=='\r') sb.append("<br>");
      else if(ch=='\t') sb.append("&nbsp;&nbsp;&nbsp&nbsp;");
      else if(ch==' ') sb.append("&nbsp;");
      else sb.append(ch);
    }
    return sb.toString();
  }

  
  
  public synchronized void logAccess(String accessType, int oc, int rc,
                                     HttpServletRequest req, String hdl,
                                     HandleValue vals[], String referrer, long time)
  {
    if (logger == null) return;
    String addr = "";
    try { addr = req.getRemoteAddr(); } catch (Throwable t) {}
    
    StringBuffer msg = new StringBuffer(50);
    msg.append(addr==null ? "" : addr);
    msg.append(' ');
    msg.append(accessType);
    msg.append(" \"");
    msg.append(new Date());
    msg.append("\" ");
    msg.append(oc);
    msg.append(' ');
    msg.append(rc);
    msg.append(' ');
    msg.append(time);
    msg.append("ms ");
    msg.append(hdl);
    
    if (logHSAdmin){
      msg.append(" \"");
      boolean firstAdmin = true;
      for (int i=0; vals!=null && i<vals.length; i++){
        if(!vals[i].hasType(Common.STD_TYPE_HSADMIN)) {
          continue;
        }
        AdminRecord adm = new AdminRecord();
        try {
          Encoder.decodeAdminRecord(vals[i].getData(), 0, adm);
        }
        catch (HandleException e){
          continue;
        }
        if (!firstAdmin) msg.append(',');
        firstAdmin = false;
        
        msg.append(adm.adminIdIndex);
        msg.append(':');
        encodeLogField(msg, Util.decodeString(adm.adminId));
      }
      msg.append('"');
    }
    
    // log the referrer
    if(logReferrer) {
      msg.append(" \"");
      encodeLogField(msg, referrer);
      msg.append('"');
    }
    
    logger.logAccess(msg.toString());
  }
  
  private static final void encodeLogField(StringBuffer sb, String str) {
    if(str==null) return;
    int strLen = str.length();
    char ch;
    for(int i=0; i<strLen; i++) {
      ch = str.charAt(i);
      if(ch=='\\')
        sb.append("\\\\");
      else
        sb.append(ch);
    }
  }
  
  
  
}
